package org.grill.clovercash.storage;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.grill.clovercash.accounts.Account;
import org.grill.clovercash.accounts.Split;
import org.grill.clovercash.accounts.Transaction;
import org.grill.clovercash.accounts.Account.Type;
import org.joda.time.LocalDate;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

public class AccountReader {
	private String filename;

	public AccountReader(String filename) {
		this.filename = filename;
	}
	
	HashMap<String, Account> accountMap = new HashMap<String, Account>();
	HashMap<String, Split> splitMap = new HashMap<String, Split>();
	
	public Account read() {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setValidating(false);
		DocumentBuilder builder;
		try {
			builder = dbf.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return null;
		}
		
		Document doc;
		try {
			doc = builder.parse(new File(filename));
		} catch (SAXException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		
		Node accountsNode = doc.getElementsByTagName("accounts").item(0);
		if (accountsNode == null)
			return null;
		
		Account rootAccount = new Account("ROOT");
		parseAccounts(rootAccount, accountsNode);
		
		Node transactionsNode = doc.getElementsByTagName("transactions").item(0);
		if (transactionsNode != null) 
			parseTransactions(transactionsNode);
		
		Node reconciliationsNode = doc.getElementsByTagName("reconciliations").item(0);
		if (reconciliationsNode != null)
			parseReconciliations(reconciliationsNode);
		
		return rootAccount;
	}

	private void parseReconciliations(Node reconciliationsNode) {
		for (Node childNode = reconciliationsNode.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) {
			if (childNode.getNodeName().equals("reconciliation")) {
				createNewReconciliation(childNode);
			}
		}
	}

	private void createNewReconciliation(Node recNode) {
		try {
			NamedNodeMap attributes = recNode.getAttributes();
			String dateString = attributes.getNamedItem("date").getNodeValue();
			String amountString = attributes.getNamedItem("amount").getNodeValue();
			String accountString = attributes.getNamedItem("account").getNodeValue();
			String recurseString = attributes.getNamedItem("recurse").getNodeValue();

			Account owner = accountMap.get(accountString);
			if (owner == null)
				return;

			Reconciliation rec = new Reconciliation(owner);
			rec.setDate(new LocalDate(format.parse(dateString)));
			rec.setEndAmount(new BigDecimal(amountString));
			rec.setRecurse(recurseString.equals("true"));
			owner.setReconciliation(rec);

			
			for (Node child = recNode.getFirstChild(); child != null; child = child.getNextSibling()) {
				try {
					if (child.getNodeName().equals("split")) {
						String splitId = child.getAttributes().getNamedItem("id").getTextContent();
						rec.getSplits().add(splitMap.get(splitId));
					} else if (child.getNodeName().equals("entry")) {
						NamedNodeMap entryAttrs = child.getAttributes();
						String payee = entryAttrs.getNamedItem("payee").getTextContent();
						String memo = entryAttrs.getNamedItem("memo").getTextContent();
						String date = entryAttrs.getNamedItem("date").getTextContent();
						String value = entryAttrs.getNamedItem("value").getTextContent();
						
						StatementEntry entry = new StatementEntry();
						entry.payeeName = payee;
						entry.memo = memo;
						entry.date = new LocalDate(format.parse(date));
						entry.value = new BigDecimal(value);
						rec.getStatementEntries().add(entry);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

	private void parseTransactions(Node transactionsNode) {
		for (Node childNode = transactionsNode.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) {
			if (childNode.getNodeName().equals("transaction")) {
				createNewTransaction(childNode);
			}
		}
	}

	DateFormat format = new SimpleDateFormat("MM-dd-yyyy");
	
	private void createNewTransaction(Node txNode) {
		NamedNodeMap attributes = txNode.getAttributes();
		String dateString = attributes.getNamedItem("date").getNodeValue();
		LocalDate date;
		try {
			date = new LocalDate(format.parse(dateString));
		} catch (ParseException e) {
			e.printStackTrace();
			date = new LocalDate();
		}
		String description = attributes.getNamedItem("description").getNodeValue();
		
		Transaction tx = new Transaction();
		tx.description = description;
		tx.date = date;
		tx.splits = new Split[2];
		
		boolean foundFirst = false;
		for (Node childNode = txNode.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) {
			if (childNode.getNodeName().equals("split")) {
				NamedNodeMap attr = childNode.getAttributes();
				boolean reconciled = attr.getNamedItem("reconciled").getNodeValue().equals("true");
				String ownerId = attr.getNamedItem("owner").getNodeValue();
				Account owner = accountMap.get(ownerId);
				String splitId = null;
				try {
					splitId = attr.getNamedItem("id").getNodeValue();
				} catch (Exception e) {}
				
				BigDecimal value = new BigDecimal(attr.getNamedItem("value").getNodeValue());
				
				Split s = new Split(owner, value, tx);
				if (splitId != null)
					splitMap.put(splitId, s);
				
				owner.getSplits().add(s);
				s.reconciled = reconciled;
				if (!foundFirst) {
					tx.splits[0] = s;
					foundFirst = true;
				} else {
					tx.splits[1] = s;
					break;
				}
			}
		}
	}

	private void parseAccounts(Account parent, Node accountsNode) {
		for (Node childNode = accountsNode.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) {
			if (childNode.getNodeName().equals("account")) {
				NamedNodeMap attributes = childNode.getAttributes();
				String name = attributes.getNamedItem("name").getNodeValue();
				String desc = attributes.getNamedItem("desc").getNodeValue();
				String type = attributes.getNamedItem("type").getNodeValue();
				String id = attributes.getNamedItem("id").getNodeValue();
				Account a = new Account(name, desc, Type.getType(type));
				a.setParent(parent);
				parent.getChildren().add(a);
				accountMap.put(id, a);
				parseAccounts(a, childNode);
			}
			Collections.sort(parent.getChildren(), new Comparator<Account>() {
				public int compare(Account o1, Account o2) {
					return o1.getName().compareTo(o2.getName());
				}
			});
		}
	}
	
	public static void main(String[] args) {
		@SuppressWarnings("unused")
		Account account = new AccountReader("/Users/dwatson/Library/Application Support/CloverCash/data.xml").read();
//		new AccountWriter("asdf", account).write();
	}
}
